﻿using Newtonsoft.Json;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Utility.Global.Exceptions;

namespace Utility.RabbitMQ_Service.Implementations
{
    internal class RabbitMQQueueImpl<T> : RabbitMQQueue<T>, IDisposable where T : class
    {
        private string queueName = null;
        private readonly IModel model = null;
        private readonly QueueingBasicConsumer basicConsumer = null;
        private readonly IBasicProperties properties = null;

        public RabbitMQQueueImpl(IConnection connection, string queueName)
        {
            this.queueName = queueName;
            this.model = connection.CreateModel();
            this.model.QueueDeclare(queueName, true, false, false, null);
            this.model.BasicQos(0, 1, false);
            this.basicConsumer = new QueueingBasicConsumer(this.model);
            this.model.BasicConsume(queueName, false, this.basicConsumer);
            this.properties = this.model.CreateBasicProperties();
        }

        public void Close()
        {
            try
            {
                this.model.Close();
            }
            catch (Exception ex)
            {
                throw new TechnicalProblemException("Queue konnte nicht geschlossen werden.", ex);
            }    
        }

        public void Dispose()
        {
            this.Close();
        }

        public void Purge()
        {
            this.model.QueuePurge(this.queueName);
        }

        public void Send(T data)
        {
            try
            {
                string json = JsonConvert.SerializeObject(data);
                byte[] content = System.Text.Encoding.UTF8.GetBytes(json);
                this.model.BasicPublish("", this.queueName, this.properties, content);
            }
            catch (Exception ex)
            {
                throw new TechnicalProblemException("Fehler beim Senden einer Nachricht aufgetreten.", ex);
            }
        }

        public T Receive()
        {
            try
            {
                BasicDeliverEventArgs delivery = (BasicDeliverEventArgs)this.basicConsumer.Queue.Dequeue();
                string message = System.Text.Encoding.UTF8.GetString(delivery.Body);
                T data = JsonConvert.DeserializeObject<T>(message);
                this.model.BasicAck(delivery.DeliveryTag, false);
                return data;
            }
            catch (Exception ex)
            {
                throw new TechnicalProblemException("Fehler beim Erhalten einer Nachricht aufgetreten.", ex);
            }
        }

        public T ReceiveRPC()
        {
            try
            {
                T data = null;
                while(true)
                {
                    BasicDeliverEventArgs delivery = (BasicDeliverEventArgs)this.basicConsumer.Queue.Dequeue();
                    IBasicProperties properties = delivery.BasicProperties;
                    if (this.GetCorrelationId().Equals(properties.CorrelationId))
                    {
                        string message = System.Text.Encoding.UTF8.GetString(delivery.Body);
                        data = JsonConvert.DeserializeObject<T>(message);
                        this.model.BasicAck(delivery.DeliveryTag, false);
                        break;
                    }
                }
                return data;
            }
            catch (Exception ex)
            {
                throw new TechnicalProblemException("Fehler beim Erhalten einer Nachricht aufgetreten.", ex);
            }
        }

        public void SetReplyToQueue(String replyToQueue)
        {
            this.properties.ReplyTo = replyToQueue;
        }

        public void SetCorrelationId(String correlationId)
        {
            this.properties.CorrelationId = correlationId;
        }

        public String GetCorrelationId()
        {
            return this.properties.CorrelationId;
        }
    }
}
